﻿//----------------------------------------------------------------
// Copyright (c) 2011-2013 
// All rights reserved.
// Author: Ben DaoWei 
// Contact: bendaowei@gmail.com
// Web Link: http://xnaeditor.codeplex.com
// ----------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using MdxLib.Model;

namespace UnityEngine.Graphics.Shader
{
    public class NonPhotoRealisticSettings
    {
        #region Fields

        // Name of a preset setting, for display to the user.
        public readonly string Name;

        // Is the cartoon lighting shader enabled?
        public readonly bool EnableToonShading;

        // Settings for the edge detect filter.
        public readonly bool EnableEdgeDetect;
        public readonly float EdgeWidth;
        public readonly float EdgeIntensity;

        // Settings for the pencil sketch effect.
        public readonly bool EnableSketch;
        public readonly bool SketchInColor;
        public readonly float SketchThreshold;
        public readonly float SketchBrightness;
        public readonly float SketchJitterSpeed;

        #endregion

        /// <summary>
        /// Constructs a new non-photorealistic settings descriptor.
        /// </summary>
        public NonPhotoRealisticSettings(string name, bool enableToonShading,
                                         bool enableEdgeDetect,
                                         float edgeWidth, float edgeIntensity,
                                         bool enableSketch, bool sketchInColor,
                                         float sketchThreshold, float sketchBrightness,
                                         float sketchJitterSpeed)
        {
            Name = name;
            EnableToonShading = enableToonShading;
            EnableEdgeDetect = enableEdgeDetect;
            EdgeWidth = edgeWidth;
            EdgeIntensity = edgeIntensity;
            EnableSketch = enableSketch;
            SketchInColor = sketchInColor;
            SketchThreshold = sketchThreshold;
            SketchBrightness = sketchBrightness;
            SketchJitterSpeed = sketchJitterSpeed;
        }

        /// <summary>
        /// Table of preset settings, used by the sample program.
        /// </summary>
        public static NonPhotoRealisticSettings[] PresetSettings =
        {
            new NonPhotoRealisticSettings("Cartoon", true,
                                          false, 1, 1,
                                          false, false, 0, 0, 0),

            new NonPhotoRealisticSettings("Pencil", false,
                                          true, 0.5f, 0.5f,
                                          true, false, 0.1f, 0.3f, 0.05f),

            new NonPhotoRealisticSettings("Chunky Monochrome", true,
                                          true, 1.5f, 0.5f,
                                          true, false, 0, 0.35f, 0),

            new NonPhotoRealisticSettings("Colored Hatching", false,
                                          true, 0.5f, 0.333f,
                                          true, true, 0.2f, 0.5f, 0.075f),

            new NonPhotoRealisticSettings("Subtle Edge Enhancement", false,
                                          true, 0.5f, 0.5f,
                                          false, false, 0, 0, 0),

            new NonPhotoRealisticSettings("Nothing Special", false,
                                          false, 0, 0,
                                          false, false, 0, 0, 0),
        };
    }

    public class NonPhotoRealistic : Shader
    {
        private SpriteBatch _SpriteBatch;
        private Effect _CartoonEffect;
        private Effect _PostprocessEffect;
        private Vector2 _SketchJitter = new Vector2(0, 0);
        private Texture2D _SketchTexture;
        private RenderTarget2D _SceneRenderTarget;
        private RenderTarget2D _NormalDepthRenderTarget;
        private NonPhotoRealisticSettings _Settings;

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_CartoonEffect != null)
                    _CartoonEffect.Dispose();

                if (_PostprocessEffect != null)
                    _PostprocessEffect.Dispose();

                if (_SketchTexture != null)
                    _SketchTexture.Dispose();

                if (_SceneRenderTarget != null)
                    _SceneRenderTarget.Dispose();

                if (_NormalDepthRenderTarget != null)
                    _NormalDepthRenderTarget.Dispose();

                if (_SpriteBatch != null)
                    _SpriteBatch.Dispose();
            }
            base.Dispose(disposing);
        }

        public NonPhotoRealistic(GraphicsDevice device)
            : base(device)
        {
            // do nothing
            this._Settings = NonPhotoRealisticSettings.PresetSettings[0];
        }

        public override void ResetDevice(GraphicsDevice device)
        {
            if (device == null)
                return;

            this.GraphicsDevice = device;

            // Create two custom rendertargets.
            PresentationParameters pp = this.GraphicsDevice.PresentationParameters;

            this._SceneRenderTarget = new RenderTarget2D(this.GraphicsDevice,
                                                   pp.BackBufferWidth, pp.BackBufferHeight, false,
                                                   pp.BackBufferFormat, pp.DepthStencilFormat);

            this._NormalDepthRenderTarget = new RenderTarget2D(this.GraphicsDevice,
                                                         pp.BackBufferWidth, pp.BackBufferHeight, false,
                                                         pp.BackBufferFormat, pp.DepthStencilFormat);

            this._SpriteBatch = new SpriteBatch(this.GraphicsDevice);
        }

        public override void LoadContent(Microsoft.Xna.Framework.Content.ContentManager Content)
        {
            this._PostprocessEffect = Content.Load<Effect>("Shader\\PostprocessEffect");
            this._SketchTexture = Content.Load<Texture2D>("Shader\\SketchTexture");
            this._CartoonEffect = Content.Load<Effect>("Shader\\CartoonEffect");
        }

        public override void Render()
        {
            // If we are doing edge detection, first off we need to render the
            // normals and depth of our model into a special rendertarget.
            if (this._Settings.EnableEdgeDetect)
            {
                this.GraphicsDevice.SetRenderTarget(this._NormalDepthRenderTarget);

                this.GraphicsDevice.Clear(Color.Transparent);

                //DrawModel(rotation, view, projection, "NormalDepth");
                this._CartoonEffect.CurrentTechnique = this._CartoonEffect.Techniques["NormalDepth"];
                SceneManager.Instance.Render3D();
            }

            // If we are doing edge detection and/or pencil sketch processing, we
            // need to draw the model into a special rendertarget which can then be
            // fed into the postprocessing shader. Otherwise can just draw it
            // directly onto the backbuffer.
            if (_Settings.EnableEdgeDetect || _Settings.EnableSketch)
                this.GraphicsDevice.SetRenderTarget(this._SceneRenderTarget);
            else
                this.GraphicsDevice.SetRenderTarget(null);

            this.GraphicsDevice.Clear(Color.Transparent);

            // Draw the model, using either the cartoon or lambert shading technique.
            string effectTechniqueName;

            if (_Settings.EnableToonShading)
                effectTechniqueName = "Toon";
            else
                effectTechniqueName = "Lambert";

            this._CartoonEffect.CurrentTechnique = this._CartoonEffect.Techniques[effectTechniqueName];
            SceneManager.Instance.Render3D();

            //DrawModel(rotation, view, projection, effectTechniqueName);

            // Run the postprocessing filter over the scene that we just rendered.
            if (_Settings.EnableEdgeDetect || _Settings.EnableSketch)
            {
                this.GraphicsDevice.SetRenderTarget(null);

                ApplyPostprocess();
            }

            SceneManager.Instance.ClearRenderQueue();
        }

        public override EMaterialLayerFilterMode FliterMode
        {
            set
            {
                this.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                this.GraphicsDevice.BlendState = BlendState.Opaque;
                this.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
                Effect = this._CartoonEffect;
            }
        }

        public override Matrix World
        {
            set
            {
                this._CartoonEffect.Parameters["World"].SetValue(value);
            }
        }

        public override Matrix View
        {
            set
            {
                this._CartoonEffect.Parameters["View"].SetValue(value);
            }
        }

        public override Matrix Projection
        {
            set
            {
                this._CartoonEffect.Parameters["Projection"].SetValue(value);
            }
        }

        public override Texture2D Texture
        {
            set
            {
                this._CartoonEffect.Parameters["Texture"].SetValue(value);
            }
        }

        public override float Alpha
        {
            set
            {
                return;
            }
        }

        public override bool TextureEnabled
        {
            set
            {
                this._CartoonEffect.Parameters["TextureEnabled"].SetValue(value);
            }
        }

        void ApplyPostprocess()
        {
            EffectParameterCollection parameters = this._PostprocessEffect.Parameters;
            string effectTechniqueName;

            // Set effect parameters controlling the pencil sketch effect.
            if (_Settings.EnableSketch)
            {
                parameters["SketchThreshold"].SetValue(_Settings.SketchThreshold);
                parameters["SketchBrightness"].SetValue(_Settings.SketchBrightness);
                parameters["SketchJitter"].SetValue(_SketchJitter);
                parameters["SketchTexture"].SetValue(this._SketchTexture);
            }

            // Set effect parameters controlling the edge detection effect.
            if (_Settings.EnableEdgeDetect)
            {
                Vector2 resolution = new Vector2(this._SceneRenderTarget.Width,
                                                 this._SceneRenderTarget.Height);

                Texture2D normalDepthTexture = this._NormalDepthRenderTarget;

                parameters["EdgeWidth"].SetValue(_Settings.EdgeWidth);
                parameters["EdgeIntensity"].SetValue(_Settings.EdgeIntensity);
                parameters["ScreenResolution"].SetValue(resolution);
                parameters["NormalDepthTexture"].SetValue(normalDepthTexture);

                // Choose which effect technique to use.
                if (_Settings.EnableSketch)
                {
                    if (_Settings.SketchInColor)
                        effectTechniqueName = "EdgeDetectColorSketch";
                    else
                        effectTechniqueName = "EdgeDetectMonoSketch";
                }
                else
                    effectTechniqueName = "EdgeDetect";
            }
            else
            {
                // If edge detection is off, just pick one of the sketch techniques.
                if (_Settings.SketchInColor)
                    effectTechniqueName = "ColorSketch";
                else
                    effectTechniqueName = "MonoSketch";
            }

            // Activate the appropriate effect technique.
            this._PostprocessEffect.CurrentTechnique = this._PostprocessEffect.Techniques[effectTechniqueName];

            _SpriteBatch.Begin(0, BlendState.Opaque, null, null, null, this._PostprocessEffect);
            _SpriteBatch.Draw(this._SceneRenderTarget, Vector2.Zero, Color.White);
            _SpriteBatch.End();
        }
    }
}
